<?php
namespace SuperCronManager;


/**
 * crontab格式解析工具类
 * @author jlb <497012571@qq.com>
 */
class CronParser
{
    protected static $weekMap = [
        0 => 'Sun',
        1 => 'Mon',
        2 => 'Tue',
        3 => 'Wed',
        4 => 'Thu',
        5 => 'Fri',
        6 => 'Sat',
    ];

    /**
     * 检查crontab格式是否支持
     * @param  string $cronstr 
     * @return boolean true|false
     */
    public static function check($cronstr) 
    {
        $cronstr = trim($cronstr);

        if (count(preg_split('#\s+#', $cronstr)) !== 5) {
            return false;
        }

        $reg = '#^(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)$#';
        if (!preg_match($reg, $cronstr)) {
            return false;
        }

        return true;
    }

    /**
     * 格式化crontab格式字符串
     * @param  string $cronstr
     * @param  interge $maxSize 设置返回符合条件的时间数量, 默认为1
     * @return array 返回符合格式的时间
     */
    public static function formatToDate($cronstr, $maxSize = 1) 
    {

        if (!static::check($cronstr)) {
            throw new \Exception("格式错误: $cronstr", 1);
        }

        $tags = preg_split('#\s+#', $cronstr);

        $crons = [
            'i' => static::parseTag($tags[0], 0, 59), //分钟
            'G' => static::parseTag($tags[1], 0, 23), //小时
            'j' => static::parseTag($tags[2], 1, 30), //一个月中的第几天
            'n' => static::parseTag($tags[3], 1, 12), //月份
            'D' => static::parseTag($tags[4], 0, 6), // 星期
        ];

        $crons['D'] = array_map(function($item){
            return static::$weekMap[$item];
        }, $crons['D']);

        // 对星期与日期同时存在的情况处理
        // j同时存在D的优先级判断
        if (count($crons['D']) == 7 && count($crons['j']) == 30) {
            $crons['j'] = [];
        }
        else if (count($crons['D']) == 7 && count($crons['j']) != 30) {
            $crons['D'] = [];
        }
        else if (count($crons['D']) != 7 && count($crons['j']) == 30) {
            $crons['j'] = [];
        }

        $list = [];
        $nowtime = date('Y-m-d H:i:s');

        // 月份循环深度
        $monMax = 12;

        // 如果分钟集合太大,会严重影响效率,故牺牲月份详细度.
        if (count($crons['i']) > 10) {
            $monMax = 1;
        }
        // 组装数据
        foreach ($crons['n'] as $month) {

            foreach ($crons['D'] as $week) {
                foreach ($crons['G'] as $hour){
                    foreach ($crons['i'] as $min){
                        $date = implode('-', [$month, $week, $hour, str_pad($min, 2, '0', STR_PAD_LEFT )]);
                        if ($vars = get_object_vars(date_create_from_format("n-D-G-i", $date))) {
                            // 过滤小于当前时间的数据
                            if ($vars['date'] < $nowtime) {
                                continue;
                            }
                            $list[] = substr($vars['date'], 0,19);
                        }
                        
                    }
                }
            }

            foreach ($crons['j'] as $day) {
                foreach ($crons['G'] as $hour){
                    foreach ($crons['i'] as $min){
                        $date = implode('-', [$month, $day, $hour, str_pad($min, 2, '0', STR_PAD_LEFT )]);
                        if ($vars = get_object_vars(date_create_from_format("n-j-G-i", $date))) {
                            // 过滤小于当前时间的数据
                            if ($vars['date'] < $nowtime) {
                                continue;
                            }
                            $list[] = substr($vars['date'], 0,19);
                        }
                        
                    }
                }
            }

            if (!$monMax) {
                break;
            }

            $monMax--;
        }

        $list = array_unique($list);

        sort($list);

        return array_slice(array_filter($list), 0, $maxSize);
    }
    /**
     * 解析元素
     * @param  string $tag  元素标签
     * @param  integer $tmin 最小值
     * @param  integer $tmax 最大值
     * @throws \Exception
     */
    protected static function parseTag($tag, $tmin, $tmax)
    {
        if ($tag == '*') {
            return range($tmin, $tmax);
        }

        $step = 1;
        $dateList = [];

        if (false !== strpos($tag, '/')) {
            $tmp = explode('/', $tag);
            $step = isset($tmp[1]) ? $tmp[1] : 1;
            
            $dateList = range($tmin, $tmax, $step);
        }
        else if (false !== strpos($tag, '-')) {
            list($min, $max) = explode('-', $tag);
            if ($min > $max) {
                list($min, $max) = [$max, $min];
            }
            $dateList = range($min, $max, $step);
        }
        else if (false !== strpos($tag, ',')) {
            $dateList = explode(',', $tag);
        }
        else {
            $dateList = array($tag);
        }

        // 越界判断
        foreach ($dateList as $num) {
            if ($num < $tmin || $num > $tmax) {
                throw new \Exception('数值越界');
            }
        }

        sort($dateList);

        return $dateList;
    }
}